<template>
  <div :class="['full-height', { 'd-flex' : map2Visible }]">
    <div
      ref="map1"
      :class="['map-container', customClass, { 'map-container-compare-rh' : compare }]"
    />
    <div
      class="grid-tools"
      v-show="map2Visible"
    >
      <div class="split-area">
        双图<br>对比
      </div>
      <div class="vertical-split-line" />
    </div>
    <div
      v-show="map2Visible"
      ref="map2"
      :class="['map-container', customClass, { 'map-container-compare-rh' : compare }]"
    />
    <EditTool
      ref="tool"
      @finishDraw="finishDraw"
      @redraw="redraw"
      @close="closeTool"
      @undo="undo"
      :visible="toolVisible"
      :is-global="isGlobal"
    />
  </div>
</template>

<script>
import Vue from 'vue'
import { publicPath } from '@/configs'
import { mapState } from 'vuex'
import EditTool from './edit-tool.vue'
import mapApiMixin from './mixins/map-api.mixin'
import mapEventMixin from './mixins/map-event-handlers.mixin'
import { debounce } from 'throttle-debounce'

const ol = window.ol
Vue.prototype.ol = window.ol

export default {
  name: 'GeoMap',
  data () {
    return {
      map2Visible: false,
      // 是否分屏显示
      compare: false,
      originModifyWKT: '',
      // 显示的图层类型
      geometryType: '',
      // 图层的数据类型
      mapDataType: 'wkt',
      toolVisible: false
    }
  },
  components: {
    EditTool
  },
  mixins: [mapApiMixin, mapEventMixin],
  props: {
    // 是否是全局使用的地图
    isGlobal: {
      type: Boolean,
      default: false
    },
    url: {
      type: String,
      default: publicPath + 'config/config.json'
    },
    // 自定义参数
    options: {
      type: Object,
      default: () => {}
    },
    // 自定义css类名
    customClass: {
      type: String,
      default: ''
    },
    extendPopClass: {
      type: String,
      default: ''
    },
    hasLimitArea: {
      type: Boolean,
      default: false
    },
    needRemoteLayer: {
      type: Boolean,
      default: true
    },
    // layerTreeId: {
    //   type: String,
    //   default: ''
    // },
    layerQueryParams: {
      type: Object,
      default: () => {}
    },
    needMapClick: {
      type: Boolean,
      default: false
    }
  },
  watch: {
    compare: {
      handler (v) {
        this.map2Visible = v
        if (v) {
          if (!this.map2) {
            this.initMap(this.options, this.$refs.map2, true).then((map) => {
              this.map2 = map
              this.map1.addInteraction(new ol.interaction.Synchronize({ maps: [this.map2] }))
              this.map2.addInteraction(new ol.interaction.Synchronize({ maps: [this.map1] }))
              this.emitEvent('onloadMap2', map)
              this.updateMapSize()
            })
          } else {
            this.updateMapSize()
          }
        } else {
          this.updateMapSize()
        }
      }
    }
  },
  computed: {
    ...mapState('map', ['popupDisabled', 'layerConfigs', 'zoom', 'forbidFlyArea', 'limitHeightArea'])
  },
  created () {
    this.debounceGetLimitAreaByExtent = debounce(300, this.getLimitAreaByExtent)
    this.debounceMapClickCallBack = debounce(300, this.mapClickCallback)
  },
  mounted () {
    if (this.isGlobal) {
      this.$mapManager.register(this)
    }
    this.initMap(this.options, this.$refs.map1).then((map) => {
      this.map1 = map
      // 创建地图右键菜单
      this.createContextMenu()
      this.emitEvent('onloadMap1', map)
      if (this.needMapClick) {
        this.map1.on('click', this.debounceMapClickCallBack)
      }
    })
    document.addEventListener('keyup', this.handleKeyup)
  },
  beforeDestroy () {
    document.removeEventListener('keyup', this.handleKeyup)
    this.map1 = null
    this.map2 = null
  },
  methods: {
    handleKeyup (event) {
      if (!this.toolVisible) return
      if (event.ctrlKey && event.code === 'KeyZ') {
        this.undo()
      } else if (event.shiftKey && event.code === 'KeyC' && this.isSwitchMapMode && this.switchLayers?.length) {
        for (const layer of this.switchLayers) {
          const visible = this.map1.mapUtil.getLayer(layer).getVisible()
          this.setLayerVisible(layer, !visible)
        }
      }
    },
    emitEvent (name, ...args) {
      if (this.isGlobal) {
        this.$mapManager.emit(name, ...args)
      } else {
        this.$emit(name, ...args)
      }
    },
    updateMapSize () {
      setTimeout(() => {
        if (this.map2) {
          this.map2.updateSize()
        }
        this.map1.updateSize()
      }, 200)
    },
    async initMap (options, target, isMap2) {
      //
      const popup = new ol.Overlay.Popup({
        // "tooltips", "warning" "black" "default", "tips", "shadow"
        popupClass: 'default' + (this.extendPopClass ? ' ' + this.extendPopClass : ''),
        closeBox: true,
        onshow: function () {},
        onclose: this.clearSelectedFeatures,
        positioning: 'auto',
        autoPan: true,
        autoPanAnimation: {
          duration: 250
        }
      })
      const tipsPopup = new ol.Overlay.Popup({
        popupClass: 'tips',
        closeBox: true,
        onshow: function () {},
        onclose: function () {},
        positioning: 'auto',
        autoPan: false,
        autoPanAnimation: {
          duration: 250
        }
      })
      const map = new ol.Map({
        target: target,
        interactions: ol.interaction.defaults.defaults({
          altShiftDragRotate: false,
          doubleClickZoom: false,
          pinchRotate: false
        }),
        controls: ol.control.defaults.defaults({
          attribution: false,
          zoom: false
        }).extend([
          // 将比例尺控件添加到地图中
          new ol.control.ScaleLine({
            // 比例尺默认的单位
            units: 'metric'
          })
        ]),
        overlays: [popup, tipsPopup],
        ...options
      })
      const response = await fetch(this.url)
      const json = await response.json()
      this.emitEvent('configLoaded', json)
      const view = new ol.View({
        // 初始化中心点坐标
        center: json.map.GlobalCfg.center,
        resolutions: json.map.GlobalCfg.resolutions,
        // 注意：此处指定缩放级别不能通过zoom来指定，指定了也无效，必须通过resolution来指定
        resolution: json.map.GlobalCfg.initResolution,
        projection: ol.proj.get('EPSG:4326')
      })
      map.setView(view)
      map.layerCreator = new window.LayerCreator()
      map.layerManager = new window.LayerManager()
      map.bkol = new window.BkolDraw()
      map.popup = popup
      map.tipsPopup = tipsPopup
      map.bkjson = json
      // 获取图层信息
      if (this.needRemoteLayer && !this.layerConfigs.length) {
        await this.$store.dispatch('map/getLayers', this.layerQueryParams)
        json.map.baseLayersConfig[0].layers.push(...this.layerConfigs)
      }
      map.layerManager.setCfg(json.map.baseLayersConfig, map)
      map.layerManager.initLayer()
      map.mapUtil = new window.MapUtil({ map: map, view: view, mapCfg: json.map, target: target })
      // 临时矢量图层
      const graphicLayer = map.mapUtil.getLayer('graphicLayer')
      const styles = []
      styles.push(new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#ff0000',
          width: 2
        }),
        image: new ol.style.Circle({
          radius: 4,
          fill: new ol.style.Fill({
            color: '#ffff'
          }),
          stroke: new ol.style.Stroke({
            color: '#ff0000',
            width: 2
          })
        })
      }))
      graphicLayer.setStyle(styles)
      map.on('singleclick', (e) => {
        this.$root.$emit('mapClick', e.coordinate)
        if (this.map1.popup) {
          const feature = this.map1.forEachFeatureAtPixel(e.pixel, (feature, layer) => {
            return feature
          })
          !feature && this.map1.popup.hide()
        }
      })
      map.on('pointermove', (e) => {
        this.$store.commit('map/setPointer', e.coordinate_)
      })
      map.on('moveend', (e) => {
        const { extent } = map.getView().getViewStateAndExtent()
        this.debounceGetLimitAreaByExtent(extent)
      })
      this.$store.commit('map/setZoom', view.getZoom())
      view.on('change:resolution', ({ target }) => {
        this.$store.commit('map/setZoom', target.values_.zoom)
        this.changeZoom(target.values_.zoom)
        const { extent } = map.getView().getViewStateAndExtent()
        this.debounceGetLimitAreaByExtent(extent)
      })
      if (!isMap2 && this.isGlobal) {
        setTimeout(() => {
          map.mapUtil.mapFull()
        }, 200)
      }
      const layers = await map.layerManager.getAllLayer()
      for (const id of layers) {
        const layer = map.mapUtil.getLayer(id)
        this.isGlobal && this.$store.commit('map/updateLayerZIndex', { layerId: id, zIndex: layer.getZIndex() })
      }
      this.oriLayers = layers
      return map
    },
    changeZoom (zoom) {
      const layers = this.map1.getAllLayers().filter(item => item.get('hasText'))
      if (zoom >= window.serverConfigs.styleChangeZoom) {
        for (const layer of layers) {
          const features = layer.getSource().getFeatures()
          features.forEach(item => {
            const raw = item.getProperties()
            const normalStyle = item.get('normalStyle')
            const styles = [normalStyle]
            if (item.get('textStyle')) {
              styles.push(item.get('textStyle'))
            } else {
              styles.push(new window.ol.style.Style({
                text: new window.ol.style.Text({
                  font: 'bold 14px sans-serif',
                  text: item.get('textKey') ? (raw[item.get('textKey')] || layer.get('bklayerId')) : layer.get('bklayerId'),
                  fill: new window.ol.style.Fill({
                    color: 'white'
                  }),
                  stroke: new window.ol.style.Stroke({
                    color: '#333',
                    width: 2
                  }),
                  offsetY: 10
                })
              }))
            }
            item.setStyle(styles)
          })
        }
      } else {
        for (const layer of layers) {
          const features = layer.getSource().getFeatures()
          features.forEach(item => {
            item.setStyle(item.get('normalStyle'))
          })
        }
      }
    },
    /**
     * 完成圈画
     */
    finishDraw () {
      const features = this.map1.mapUtil.getLayer('modifylayer').getSource().getFeatures()
      if (!features[0]) return
      let result = this.transfromFeature(features[0], false)
      const type = features[0].getGeometry().getType()
      let area, length, coordinate
      if (!this.drawMultiple) {
        coordinate = this.map1.bkol.getCenterPoint(features[0])
        switch (type) {
          case 'Point':
            this.emitEvent(`mapModifyEnd${this.drawEventKey ? '-' + this.drawEventKey : ''}`, { result, coordinate })
            break
          case 'LineString':
            length = this.map1.mapUtil.getLength(features[0].getGeometry())
            length = parseFloat(length)
            this.emitEvent(`mapModifyEnd${this.drawEventKey ? '-' + this.drawEventKey : ''}`, { result, length, coordinate })
            break
          case 'Polygon':
            area = this.map1.mapUtil.getAreaByHm(features[0].getGeometry())
            area = parseFloat(area)
            this.emitEvent(`mapModifyEnd${this.drawEventKey ? '-' + this.drawEventKey : ''}`, { result, area, coordinate })
            break
          default:
            break
        }
      } else {
        let finalFeature = features[0]
        if (features.length > 1) {
          finalFeature = this.map1.bkol.mergePolygon(features)
          result = this.map1.bkol.feature2Wkt(finalFeature)
        }
        coordinate = this.map1.bkol.getCenterPoint(finalFeature)
        area = parseFloat(this.map1.mapUtil.getAreaByHm(finalFeature.getGeometry()))
        this.emitEvent(`mapModifyEnd${this.drawEventKey ? '-' + this.drawEventKey : ''}`, { result, area, coordinate })
      }
      this.drawEventKey = ''
      this.closeTool(false)
    },
    /**
     * 重画
     */
    redraw () {
      if (this.originModifyWKT) {
        const feature = this.transfromFeature(this.originModifyWKT, true)
        const newFeature = feature.clone()
        newFeature.setStyle(this.map1.mapUtil.getPointEditStyle())
        const layer = this.map1.mapUtil.getLayer('modifylayer')
        layer.getSource().clear()
        // 设置点的样式
        if (!this.isGlobal && newFeature.getGeometry().getType() === 'Point') {
          newFeature.setStyle(this.map1.mapUtil.getPointImgStyle(require('@/components/geo-map/images/icon.png'), 1))
        }
        layer.getSource().addFeature(newFeature)
      } else {
        this.clearLayers()
        this.drawInteraction && this.drawInteraction.setActive(true)
      }
    },
    /**
     * 关闭工具栏
     */
    closeTool (isClose = true) {
      if (!this.toolVisible) return
      this.map1.mapUtil.getLayer('modifylayer').getSource().clear()
      if (this.isGlobal) {
        this.$store.commit('settings/setLayoutVisible', true)
      }
      this.toolVisible = false
      this.closeDraw()
      this.closeModify()
      this.emitEvent('closeMapTool', isClose)
    },
    /**
     * 撤销上一步
     */
    undo () {
      // 圈画
      this.drawInteraction && this.drawInteraction.removeLastPoint()
      if (this.undoRedoInteraction) {
        if (this.undoRedoInteraction.hasUndo()) {
          this.undoRedoInteraction.undo()
        } else {
          this.redraw()
        }
      }
    },
    /**
     * 调整缩放
     */
    setZoom (type = 'fangda', scale = 0.5) {
      const zoom = this.map1.getView().getZoom()
      if (type === 'fangda') {
        this.map1.getView().setZoom(zoom + scale)
      } else if (type === 'suoxiao') {
        this.map1.getView().setZoom(zoom - scale)
      } else if (type === 'quanmao') {
        if (this.maskGeometry) {
          this.fit(this.maskGeometry)
        } else {
          this.map1.mapUtil.mapFull()
        }
      }
    },
    /**
     * 单击事件图层过滤方法，非基础图层需要触发回调方法
     */
    selectFilter (feature, layer) {
      const layerIds = this.map1.layerManager.getAllLayer().filter(l => !this.oriLayers.includes(l))
      return layer != null && layerIds.indexOf(layer.get('bklayerId')) > -1 && (layer.get('isClick') || layer.get('bklayerId').endsWith('_cluster'))
    },
    /**
     * 鼠标移入事件图层过滤方法，非基础图层并且是点类型的需要触发回调方法
     */
    selectFilterByfilter (feature, layer) {
      const layerIds = this.map1.layerManager.getAllLayer().filter(l => !this.oriLayers.includes(l))
      return layer != null && layerIds.indexOf(layer.get('bklayerId')) > -1 && feature.getGeometry().getType() === 'Point' && layer.get('isHover')
    },
    /**
     * 单击事件和鼠标移入事件选中样式
     */
    selectStyle (feature) {
      const styleFunction = feature.get('selectStyle')
      if (styleFunction) {
        return styleFunction(feature)
      } else {
        const properties = feature.getProperties()
        if (properties.img) {
          const iconOption = new window.ol.style.Icon({
            offset: [0, 0],
            opacity: 1,
            scale: 0.8,
            src: properties.img,
            size: [58, 200]
          })
          const option = { image: iconOption }
          return new window.ol.style.Style(option)
        } else {
          return this.defaultEditStyle(feature)
        }
      }
    },
    defaultEditStyle (feature) {
      const type = feature.getGeometry().getType()
      const styles = {}
      const white = [255, 255, 255, 1]
      const blue = [0, 153, 255, 1]
      const width = 3
      styles.Polygon = [
        new window.ol.style.Style({
          fill: new window.ol.style.Fill({
            color: [255, 255, 255, 0.5]
          })
        })
      ]
      styles.MultiPolygon = styles.Polygon
      styles.LineString = [
        new window.ol.style.Style({
          stroke: new window.ol.style.Stroke({
            color: white,
            width: width + 2
          })
        }),
        new window.ol.style.Style({
          stroke: new window.ol.style.Stroke({
            color: blue,
            width: width
          })
        })
      ]
      styles.MultiLineString = styles.LineString
      styles.Circle = styles.Polygon.concat(styles.LineString)
      styles.Point = [
        new window.ol.style.Style({
          image: new window.ol.style.Circle({
            radius: width * 2,
            fill: new window.ol.style.Fill({
              color: blue
            }),
            stroke: new window.ol.style.Stroke({
              color: white,
              width: width / 2
            })
          }),
          zIndex: Infinity
        })
      ]
      styles.MultiPoint = styles.Point
      styles.GeometryCollection = styles.Polygon.concat(styles.LineString, styles.Point)
      return styles[type]
    },
    /**
     * 创建地图右键菜单
     */
    createContextMenu () {
      this.contextMenu = new window.ContextMenu({
        width: 200,
        defaultItems: false,
        items: []
      })
      this.contextMenu.on('beforeopen', this.menuBeforeOpen)
      this.map1.addControl(this.contextMenu)
    },
    menuBeforeOpen (event) {
      let layerId = ''
      const feature = this.map1.forEachFeatureAtPixel(event.pixel, (feature, layer) => {
        layerId = layer.get('bklayerId')
        return feature
      })
      this.setContextMenuItems(feature, layerId, event)
    },
    setContextMenuItems (feature, layerId, event) {
      this.contextMenu.closeMenu()
      this.contextMenu.clear()
      const items = this.setCurrentContextMenu(feature, layerId, event)
      if (items.length > 0) {
        this.contextMenu.extend(items)
        this.contextMenu.enable()
      } else {
        this.contextMenu.disable()
      }
    },
    // 默认的创建菜单项的方法，各个用到的地方需要调用方法覆盖
    setCurrentContextMenu (feature, layerId, event) {
      return []
    },
    // 获取禁飞区和限高区
    async getLimitAreaByExtent (extent) {
      if (this.hasLimitArea) {
        await this.$store.dispatch('map/getArea', extent)
        this.renderForbiddenAndLimitArea()
      }
    },
    // 地图渲染无人机限高区和禁飞区
    renderForbiddenAndLimitArea () {
      const forbiddenLayer = this.map1.mapUtil.getLayer('forbidden_area')
      const limitLayer = this.map1.mapUtil.getLayer('limit_height_area')
      forbiddenLayer.getSource().clear()
      limitLayer.getSource().clear()
      const forbiddenStyle = new window.ol.style.Style({
        stroke: new window.ol.style.Stroke({
          color: 'rgb(255, 0, 2)',
          width: 1
        }),
        fill: new window.ol.style.Fill({ color: 'rgba(255, 0, 2, 0.3)' })
      })
      const limitStyle = new window.ol.style.Style({
        stroke: new window.ol.style.Stroke({
          color: 'rgb(255, 90, 2)',
          width: 1
        }),
        fill: new window.ol.style.Fill({ color: 'rgba(255, 90, 2, 0.3)' })
      })
      // 禁飞区
      const forbiddenFeature = new window.ol.Feature()
      const forbiddenGeometry = new window.ol.geom.MultiPolygon(this.forbidFlyArea)
      forbiddenFeature.setGeometry(forbiddenGeometry)
      forbiddenFeature.setStyle(forbiddenStyle)
      forbiddenLayer.getSource().addFeature(forbiddenFeature)
      // 限高区
      const limitFeature = new window.ol.Feature()
      const limitGeometry = new window.ol.geom.MultiPolygon(this.limitHeightArea)
      limitFeature.setGeometry(limitGeometry)
      limitFeature.setStyle(limitStyle)
      limitLayer.getSource().addFeature(limitFeature)
    }
  }
}
</script>

<style  lang="scss" scoped>
.map-container {
  height: 100%;
  overflow: hidden;
}

.map-container-compare-rh {
  display: inline-block;
  width: 50%;
}

.grid-tools {
  .split-area {
    position: fixed;
    z-index: 10;
    left: calc(50% - 30px);
    top: 68px;
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background: #142343;
    border: 6px solid #1c52a5;
    color: #ffd517;
    font-size: 14px;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .vertical-split-line {
    width: 6px;
    position: fixed;
    z-index: 2;
    background: #1c52a5;
    top: 120px;
    left: calc(50% - 3px);
    bottom: 0;
  }

  .split-area,
  .vertical-split-line {
    transition: transform 0.2s;
  }
}
</style>

<style lang="scss">
.common-layout-mode {
  .grid-tools {
    .split-area,
    .vertical-split-line {
      transform: translateX($sidebarWidth / 2);
    }
  }

  &.sidebar-collapsed {
    .grid-tools {
      .split-area,
      .vertical-split-line {
        transform: translateX($sidebarCollapsedWidth / 2);
      }
    }
  }
}
</style>
